import { Component, Vec2, _decorator, isValid, view, Node, UITransform, rect, Rect, Vec3, tween, Tween, v2, Graphics, Color, v3 } from 'cc';

const { ccclass, property } = _decorator;

@ccclass('Utils')
class Utils extends Component {
  private static _instance: Utils;
  public static get instance() {
    if (this._instance == null) {
      this._instance = new Utils();
    }
    return this._instance;
  }
  /**
   * 分帧执行
   * @param fun     执行函数
   * @param target  执行函数对象
   * @param start   起始次数     (从0开始，0表示执行第1次)
   * @param max     最大执行次数 (例如5则表示函数总共执行5次)
   * @param executeTime   分配的执行时间 (默认8毫秒，即60帧率时，一帧时间的一半)
   * @param data    传递数据
   * @returns
   */
  public async execute(fun: Function, target: any, start: number, max: number, executeTime = 8, data: any = null) {
    // 如果target是一个界面，那么界面关闭时停止执行
    if (target.node && !target.node.active) return;
    //获取开始时间
    var startTime = new Date().getTime();
    //执行计数
    var count = start;
    //开始执行函数，如果超过分配的执行时间，则延迟到下一帧执行
    for (var i = count; i < max; i++) {
      if (!target || !isValid(target)) return;
      //执行函数
      fun.call(target, count, data);
      //超过最大执行次数，则退出
      count++;
      if (count >= max) {
        return;
      }
      //获取消耗时间
      var costTime = new Date().getTime() - startTime;
      // console.log("执行耗时:", costTime);
      //消耗时间 > 分配的时间，则延迟到下一帧执行
      if (costTime > executeTime) {
        // console.log("超时，进入下一轮加载")
        this.scheduleOnce(() => {
          this.execute(fun, target, count, max, executeTime, data);
        });
        return;
      }
    }
  }

  getStrByLen(str: string, len: number) {
    // 获取字符串相应长度的截取，中文占2个长度，非中文占1个长度
    let curlen = 0;
    let ind = 0;
    for (let i = 0; i < str.length; i++) {
      let s = str[i];
      if (s.match(/[\u4e00-\u9fa5]/g)) {
        curlen += 2;
      } else {
        curlen += 1;
      }
      if (curlen >= len) {
        ind = i;
        break;
      }
    }
    if (ind === 0 || ind === str.length - 1) {
      return str;
    } else {
      return str.substring(0, ind + 1) + '...';
    }
  }

  //将节点转换成左上角为原点 (即微信原生坐标系)
  turnToLeftAxis(node, platformSize) {
    let winSize = view.getVisibleSize();

    let rateW = platformSize.screenWidth / winSize.width;
    let rateH = platformSize.screenHeight / winSize.height;

    let worldPos = node.convertToWorldSpaceAR(new Vec2(0, 0));
    let x = worldPos.x - winSize.width / 2;
    let y = worldPos.y - winSize.height / 2;

    x *= rateW;
    y *= rateH;

    let nodeW = node.width * rateW + 10; //稍微大一点
    let nodeH = node.height * rateH + 10;

    x = x + platformSize.screenWidth / 2 - nodeW / 2;
    y = platformSize.screenHeight / 2 - (y + nodeH / 2);

    return { left: x, top: y, width: nodeW, height: nodeH };
  }

  /** 计算uv, 锚点都是中心 */
  public static computeUv(points: Vec2[], width: number, height: number) {
    let uvs: Vec2[] = [];
    // for (const p of points) {
    //     // uv原点是左上角
    //     let x = MathUtils.clamp(0, 1, (p.x + width / 2) / width);
    //     let y = MathUtils.clamp(0, 1, 1. - (p.y + height / 2) / height);
    //     uvs.push(new Vec2(x, y));
    // }
    return uvs;
  }

  // 判断一个点是否在三角形内
  public static isInTriangle(point: Vec2, triA: Vec2, triB: Vec2, triC: Vec2) {
    let AB = triB.clone().subtract(triA);
    let AC = triC.clone().subtract(triA);
    let BC = triC.clone().subtract(triB);
    let AD = point.clone().subtract(triA);
    let BD = point.clone().subtract(triB);
    //@ts-ignore
    return (AB.cross(AC) >= 0) ^ (AB.cross(AD) < 0) && (AB.cross(AC) >= 0) ^ (AC.cross(AD) >= 0) && (BC.cross(AB) > 0) ^ (BC.cross(BD) >= 0);
  }

  // 多边形 三角切割
  public static splitePolygon(points: Vec2[]): number[] {
    if (points.length <= 3) return [0, 1, 2];
    let pointMap: { [key: string]: number } = {}; // point与idx的映射
    for (let i = 0; i < points.length; i++) {
      let p = points[i];
      pointMap[`${p.x}-${p.y}`] = i;
    }
    const getIdx = (p: Vec2) => {
      return pointMap[`${p.x}-${p.y}`];
    };
    points = points.concat([]);
    let idxs: number[] = [];

    let index = 0;
    while (points.length > 3) {
      let p1 = points[index % points.length],
        p2 = points[(index + 1) % points.length],
        p3 = points[(index + 2) % points.length];
      let splitPoint = (index + 1) % points.length;

      let v1 = p2.clone().subtract(p1);
      let v2 = p3.clone().subtract(p2);
      if (v1.cross(v2) < 0) {
        // 是一个凹角, 寻找下一个
        index = (index + 1) % points.length;
        continue;
      }
      let hasPoint = false;
      for (const p of points) {
        if (p != p1 && p != p2 && p != p3 && this.isInTriangle(p, p1, p2, p3)) {
          hasPoint = true;
          break;
        }
      }
      if (hasPoint) {
        // 当前三角形包含其他点, 寻找下一个
        index = (index + 1) % points.length;
        continue;
      }
      // 找到了耳朵, 切掉
      idxs.push(getIdx(p1), getIdx(p2), getIdx(p3));
      points.splice(splitPoint, 1);
    }
    for (const p of points) {
      idxs.push(getIdx(p));
    }
    return idxs;
  }

  public static isInPolygon(checkPoint: Vec2, polygonPoints: Vec2[]) {
    let counter = 0,
      i: number,
      xinters: number;
    let p1: Vec2, p2: Vec2;
    let pointCount = polygonPoints.length;
    p1 = polygonPoints[0];

    for (i = 1; i <= pointCount; i++) {
      p2 = polygonPoints[i % pointCount];
      if (checkPoint.x > Math.min(p1.x, p2.x) && checkPoint.x <= Math.max(p1.x, p2.x)) {
        if (checkPoint.y <= Math.max(p1.y, p2.y)) {
          if (p1.x != p2.x) {
            xinters = ((checkPoint.x - p1.x) * (p2.y - p1.y)) / (p2.x - p1.x) + p1.y;
            if (p1.y == p2.y || checkPoint.y <= xinters) {
              counter++;
            }
          }
        }
      }
      p1 = p2;
    }
    return (counter & 1) !== 0;
  }

  //判断俩个节点是否相交
  twoNodeCollision(target: Node, otherTarget: Node) {
    let rect1 = target.getComponent(UITransform).getBoundingBox();
    let rect2 = otherTarget.getComponent(UITransform).getBoundingBox();
    return rect1.intersects(rect2);
  }

  //判断俩个节点是否相交
  twoNodeCollisionToWorld(target: Node, otherTarget: Node) {
    let rect1 = target.getComponent(UITransform).getBoundingBoxToWorld();
    let rect2 = otherTarget.getComponent(UITransform).getBoundingBoxToWorld();
    return rect1.intersects(rect2);
  }

  //修复CollisionToWorld
  twoNodeCollisionToWorld2(target: Node, otherTarget: Node) {
    let rect1 = this.nodeGetBoundingBox(target);
    let rect2 = this.nodeGetBoundingBox(otherTarget);
    let pos1 = target.convertToWorldSpaceAREx(new Vec3(0, 0));
    let pos2 = otherTarget.convertToWorldSpaceAREx(new Vec3(0, 0));

    let newRect1 = new Rect(pos1.x - rect1.width / 2, pos1.y - rect1.height / 2, rect1.width, rect1.height);
    let newRect2 = new Rect(pos2.x - rect2.width / 2, pos2.y - rect2.height / 2, rect2.width, rect2.height);
    return newRect1.intersects(newRect2);
  }

  delayTime(time = 0) {
    return new Promise((resolve) => {
      this.scheduleOnce(resolve, time);
    });
  }

  // 打乱数组，洗牌算法
  shuffleList(list: any[]) {
    for (let i = 0; i < list.length; i++) {
      let r = Math.floor(Math.random() * list.length);
      let temp = list[i];
      list[i] = list[r];
      list[r] = temp;
    }
  }

  nodeGetBoundingBox(target: Node) {
    if (!target.getComponent(UITransform)) return new Rect(0, 0, 0, 0);
    return target.getComponent(UITransform).getBoundingBox();
  }

  nodeGetgetBoundingBoxToWorld(target: Node) {
    if (!target.getComponent(UITransform)) return new Rect(0, 0, 0, 0);
    return target.getComponent(UITransform).getBoundingBoxToWorld();
  }

  /**
   * @desc 三阶阶贝塞尔
   * @param {number} duration 归一量百分比
   * @param {Vec3} p1 起点坐标
   * @param {Vec3} cp 控制点
   * @param {Vec3} p2 终点坐标
   * @param {object} opts
   * @returns {any}
   */
  bezierTo(target: any, duration: number, p1: Vec3, cp: Vec3, p2: Vec3, opts?: any): Tween<any> {
    opts = opts || Object.create(null);
    let twoBezier = (t: number, p1: Vec3, cp: Vec3, p2: Vec3) => {
      let x = (1 - t) * (1 - t) * p1.x + 2 * t * (1 - t) * cp.x + t * t * p2.x;
      let y = (1 - t) * (1 - t) * p1.y + 2 * t * (1 - t) * cp.y + t * t * p2.y;
      let z = (1 - t) * (1 - t) * p1.z + 2 * t * (1 - t) * cp.z + t * t * p2.z;
      return new Vec3(x, y, z);
    };
    opts.onUpdate = (_arg: Vec3, ratio: number) => {
      target.position = twoBezier(ratio, p1, cp, p2);
    };
    return tween(target).to(duration, {}, opts);
  }

  getSignAngle(start: Vec3 | Vec2, end: Vec3 | Vec2) {
    //计算出朝向
    var dx = end.x - start.x;
    var dy = end.y - start.y;
    var dir = v2(dx, dy);
    //根据朝向计算出夹角弧度
    var radian = dir.signAngle(v2(1, 0));
    //将弧度转换为欧拉角
    var angle = (radian * 180) / Math.PI;

    return -angle;
  }

  /**获取边界点位置 */
  getRayIntersectionWithRectangle(width: number, height: number, angleDegrees: number, dis: number): Vec3 {
    // 将角度转换为弧度（TypeScript/JavaScript的三角函数使用弧度）
    const angleRadians = (angleDegrees * Math.PI) / 180;

    // 计算射线的方向向量
    const dirX = Math.sin(angleRadians);
    const dirY = Math.cos(angleRadians);

    // 矩形半宽和半高
    const halfWidth = width / 2;
    const halfHeight = height / 2;

    // 计算射线与矩形四条边的交点参数t
    // 我们需要找到最小的正t值，即第一个与矩形相交的点

    // 与左右边（x = ±halfWidth）的交点
    let tX = Infinity;
    if (Math.abs(dirX) > 1e-6) {
      // 避免除以0
      const tLeft = -halfWidth / dirX;
      const tRight = halfWidth / dirX;
      // 选择正的t值
      if (tLeft > 0) tX = tLeft;
      if (tRight > 0 && tRight < tX) tX = tRight;
    }

    // 与上下边（y = ±halfHeight）的交点
    let tY = Infinity;
    if (Math.abs(dirY) > 1e-6) {
      // 避免除以0
      const tTop = halfHeight / dirY;
      const tBottom = -halfHeight / dirY;
      // 选择正的t值
      if (tTop > 0) tY = tTop;
      if (tBottom > 0 && tBottom < tY) tY = tBottom;
    }

    // 取最小的t值，即第一个交点
    const t = Math.min(tX, tY);

    // 如果最小t值大于dis，说明射线在dis长度内未碰到边界，返回null
    if (t > dis) {
      return null;
    }

    // 计算交点坐标
    const intersectionX = dirX * t;
    const intersectionY = dirY * t;

    return v3(intersectionX, intersectionY);
  }

  calculateVectorAngle(vector: Vec3): number {
    // 计算与正x轴的夹角（Math.atan2的默认行为）
    const angleWithXAxis = Math.atan2(vector.y, vector.x);

    // 转换为与正y轴的夹角（0对应正上方）
    let angleWithYAxis = Math.PI / 2 - angleWithXAxis;

    // 确保角度在[0, 2π)范围内
    if (angleWithYAxis < 0) {
      angleWithYAxis += 2 * Math.PI;
    }

    return angleWithYAxis;
  }
  /**
   * 根据向量求角度
   * @param vector
   * @param inDegrees
   * @returns
   */
  calculateVectorAngleDegrees(vector: Vec3): number {
    const radians = this.calculateVectorAngle(vector);
    return radians * (180 / Math.PI);
  }
}
export default Utils.instance;
